﻿namespace TuneMultiCheckBox
{
  using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.Diagnostics.CodeAnalysis;
  using System.Globalization;
  using System.Linq;
  using Entities;
  using Exception;
  using Utils;
  using Microsoft.Xrm.Sdk;

  /// <summary>
  /// Extended Base class for Plug-ins.
  /// </summary>
  /// <typeparam name="TEntity">The type of the entity.</typeparam>
  /// <remarks>
  /// Just inherit from PluginBase<Entity/> in case the plugin is to handle different entities in a single plugin 
  /// </remarks>
  public abstract class PluginBase<TEntity> : IPlugin
    where TEntity : Entity, new()
  {
    /// <summary>
    /// The default pre image name
    /// </summary>
    public const string DefaultPreImageName = "PreImage";

    /// <summary>
    /// The default post image name
    /// </summary>
    public const string DefaultPostImageName = "PostImage";

    /// <summary>
    /// The entity logical name (the name is cached)
    /// </summary>
    public readonly string EntityLogicalName;

    /// <summary>
    /// The secure config
    /// </summary>
    private readonly string secureConfig;

    /// <summary>
    /// The unsecure config
    /// </summary>
    private readonly string unsecureConfig;

    /// <summary>
    /// The Backing Field for <seealso cref="RegisteredEvents"/>
    /// </summary>
    private Collection<Tuple<int, string, string, Action<LocalPluginContext>>> registeredEvents;

    /// <summary>
    /// The Backing Field for <seealso cref="RegisteredPluginSteps"/>
    /// </summary>
    private IList<PluginStepBase> registeredPluginSteps;

    /// <summary>
    /// Initializes a new instance of the <see cref="PluginBase{TEntity}" /> class.
    /// </summary>
    /// <param name="childClassName">Name of the child class.</param>
    /// <param name="unsecureConfig">The unsecure config.</param>
    /// <param name="secureConfig">The secure config.</param>
    internal PluginBase(Type childClassName, string unsecureConfig = null, string secureConfig = null)
    {
      var entityType = typeof(TEntity);
      this.EntityLogicalName = 
        entityType == typeof(Entity) ? string.Empty : typeof(TEntity).Name.ToLower();
      
      this.ChildClassName = childClassName.ToString();
      this.unsecureConfig = unsecureConfig;
      this.secureConfig = secureConfig;
    }

    /// <summary>
    /// Gets the registered plugin steps.
    /// </summary>
    /// <value>
    /// The registered plugin steps.
    /// </value>
    protected IList<PluginStepBase> RegisteredPluginSteps
    {
      get
      {
        return this.registeredPluginSteps ?? (this.registeredPluginSteps = new List<PluginStepBase>());
      }
    }

    /// <summary>
    /// Gets the List of events that the plug-in should fire for. Each List
    /// Item is a <see cref="System.Tuple"/> containing the Pipeline Stage, Mode, Message and (optionally) the Primary Entity. 
    /// In addition, the fourth parameter provide the delegate to invoke on a matching registration.
    /// </summary>
    [Obsolete("This property is obsolete. Please consider using RegisteredPluginSteps instead.")]
    protected Collection<Tuple<int, string, string, Action<LocalPluginContext>>> RegisteredEvents
    {
      get
      {
        return this.registeredEvents ??
               (this.registeredEvents = new Collection<Tuple<int, string, string, Action<LocalPluginContext>>>());
      }
    }

    /// <summary>
    /// Gets the name of the child class.
    /// </summary>
    /// <value>The name of the child class.</value>
    protected string ChildClassName { get; private set; }

    /// <summary>
    /// Executes the plug-in.
    /// </summary>
    /// <param name="serviceProvider">The service provider.</param>
    /// <remarks>
    /// For improved performance, Microsoft Dynamics CRM caches plug-in instances. 
    /// The plug-in's Execute method should be written to be stateless as the constructor 
    /// is not called for every invocation of the plug-in. Also, multiple system threads 
    /// could execute the plug-in at the same time. All per invocation state information 
    /// is stored in the context. This means that you should not use global variables in plug-ins.
    /// </remarks>
    public void Execute(IServiceProvider serviceProvider)
    {
      if (serviceProvider == null)
      {
        throw new ArgumentNullException("serviceProvider");
      }

      // Construct the Local plug-in context.
      var localcontext = new LocalPluginContext(serviceProvider, this.unsecureConfig, this.secureConfig);

      var stage = localcontext.PluginExecutionContext.Stage;
      var mode = localcontext.PluginExecutionContext.Mode;
      var messageName = localcontext.PluginExecutionContext.MessageName;
      var primaryEntityName = localcontext.PluginExecutionContext.PrimaryEntityName;

      localcontext.Trace(
        string.Format(
          CultureInfo.InvariantCulture,
          "{0} is firing for Entity: {1}, Message: {2}, Stage: {3}, Mode: {4}",
          this.ChildClassName,
          primaryEntityName,
          messageName,
          stage,
          mode));

      try
      {
        Func<PluginStepBase, bool> stepSearchCondition =
          step =>
            (step.Stage == null || (int)step.Stage == stage)
              && (step.Mode == null || (int)step.Mode == mode)
              && (step.MessageName == null || step.MessageName.ToString() == messageName)
              && (string.IsNullOrWhiteSpace(this.EntityLogicalName) || this.EntityLogicalName == primaryEntityName);

        var entityAction =
          this.RegisteredPluginSteps.Where(stepSearchCondition).Select(step => step.Handler).FirstOrDefault();

        if (entityAction == null)
        {
          // checks for the old native step registrations
          entityAction = (from tuple in this.RegisteredEvents
                          where
                            (tuple.Item1 == stage &&
                              tuple.Item2 == messageName &&
                              (string.IsNullOrWhiteSpace(tuple.Item3) || tuple.Item3 == primaryEntityName))
                          select tuple.Item4)
            .FirstOrDefault();
        }

        if (entityAction == null)
        {
          localcontext.Trace("No appropriate registered action found.");
        }
        else
        {
          localcontext.Trace("An appropriate registered action has been found. Executing the action...");
          entityAction(localcontext);
        }
      }
      catch (PluginIgnoredException ignoredException)
      {
        ExceptionHelper.BuildInvalidPluginExecutionException(ignoredException, this.GetType(), localcontext.TracingService);
      }
      catch (System.Exception e)
      {
        throw ExceptionHelper.BuildInvalidPluginExecutionException(e, this.GetType(), localcontext.TracingService);
      }
      finally
      {
        localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Exiting {0}.Execute()", this.ChildClassName));
      }
    }

    /// <summary>
    /// Defines the local plugin context class.
    /// </summary>
    public class LocalPluginContext
    {
      /// <summary>
      /// The secure config
      /// </summary>
      internal readonly string SecureConfig;

      /// <summary>
      /// The unsecure config
      /// </summary>
      internal readonly string UnsecureConfig;

      private const string TargetKey = "Target";

      private const string BusinessEntityKey = "BusinessEntity";

      private const string EntityMonikerKey = "EntityMoniker";

      private const string StateKey = "State";

      private const string StatusKey = "Status";

      private const string ParameterXmlKey = "ParameterXml";

      private const string RelatedEntitiesKey = "RelatedEntities";

      private const string RelationshipKey = "Relationship";

      /// <summary>
      /// The organization service factory
      /// </summary>
      private readonly IOrganizationServiceFactory organizationServiceFactory;

      /// <summary>
      /// The service provider
      /// </summary>
      private readonly IServiceProvider serviceProvider;

      /// <summary>
      /// Backing Field for <see cref="OrganizationService" />
      /// </summary>
      private IOrganizationService organizationService;

      /// <summary>
      /// Backing Field for <see cref="OrganizationServiceAsSystemUser" />
      /// </summary>
      private IOrganizationService organizationServiceAsSystemUser;

      /// <summary>
      /// Backing Field for <see cref="InputTarget"/>
      /// </summary>
      private TEntity inputTarget;

      /// <summary>
      /// Backing Field for  <see cref="BaseEntityLogicalName"/>
      /// </summary>
      private string inputTargetEntityName;

      /// <summary>
      /// Backing Field for  <see cref="InputTargetEntity"/>
      /// </summary>
      private Entity inputTargetEntity;

      /// <summary>
      /// Backing Field for  <see cref="OutputBusinessEntityEntity"/>
      /// </summary>
      private Entity outputBusinessEntityEntity;

      /// <summary>
      /// Backing Field for  <see cref="OutputBusinessEntity"/>
      /// </summary>
      private TEntity outputBusinessEntity;

      /// <summary>
      /// Backing Field for  <see cref="Relationship"/>
      /// </summary>
      private Relationship relationship;

      /// <summary>
      /// Backing Field for  <see cref="RelatedEntities"/>
      /// </summary>
      private EntityReferenceCollection relatedEntities;

      /// <summary>
      /// Backing Field for  <see cref="InputTargetEntityReference"/>
      /// </summary>      
      private EntityReference inputTargetEntityReference;

      /// <summary>
      /// Backing Field for  <see cref="InputEntityMoniker"/>
      /// </summary>
      private EntityReference inputEntityMoniker;

      /// <summary>
      /// The input state
      /// </summary>
      private OptionSetValue inputState;

      /// <summary>
      /// The input status
      /// </summary>
      private OptionSetValue inputStatus;

      /// <summary>
      /// The parameter XML
      /// </summary>
      private string parameterXml;

      /// <summary>
      /// Initializes a new instance of the <see cref="LocalPluginContext" /> class.
      /// </summary>
      /// <param name="serviceProvider">The service provider.</param>
      /// <param name="unsecureConfig">The unsecure config.</param>
      /// <param name="secureConfig">The secure config.</param>
      /// <exception cref="System.ArgumentNullException">serviceProvider</exception>
      internal LocalPluginContext(IServiceProvider serviceProvider, string unsecureConfig, string secureConfig)
      {
        if (serviceProvider == null)
        {
          throw new ArgumentNullException("serviceProvider");
        }
        
        this.serviceProvider = serviceProvider;
        this.organizationServiceFactory = (IOrganizationServiceFactory)this.serviceProvider.GetService(typeof(IOrganizationServiceFactory));

        // TODO: redesign all to lazy load

        // Obtain the execution context service from the service provider.
        this.PluginExecutionContext =
          (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

        // Obtain the tracing service from the service provider.
        this.TracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

        this.UnsecureConfig = unsecureConfig;
        this.SecureConfig = secureConfig;
      }

      /// <summary>
      /// Prevents a default instance of the <see cref="LocalPluginContext"/> class from being created. 
      /// Initializes a new instance of the <see cref="LocalPluginContext"/> class.
      /// </summary>
      private LocalPluginContext()
      {
      }

      /// <summary>
      /// Gets the organization service for the "current user".
      /// </summary>
      /// <value>The organization service.</value>
      internal IOrganizationService OrganizationService
      {
        get
        {
          if (this.organizationService == null)
          {
            var currentUserId = this.PluginExecutionContext.UserId;
            this.organizationService = this.organizationServiceFactory.CreateOrganizationService(currentUserId);
          }

          return this.organizationService;
        }
      }

      /// <summary>
      /// Gets the plugin execution context.
      /// </summary>
      /// <value>The plugin execution context.</value>
      internal IPluginExecutionContext PluginExecutionContext { get; private set; }

      /// <summary>
      /// Gets the tracing service.
      /// </summary>
      /// <value>The tracing service.</value>
      internal ITracingService TracingService { get; private set; }

      /// <summary>
      /// Gets the organization service for a system user.
      /// </summary>
      /// <value>
      /// The organization service for system user.
      /// </value>
      internal IOrganizationService OrganizationServiceAsSystemUser
      {
        get
        {
          return this.organizationServiceAsSystemUser ?? 
            (this.organizationServiceAsSystemUser = this.organizationServiceFactory.CreateOrganizationServiceAsSystemUser(this.PluginExecutionContext.UserId));
        }
      }

      /// <summary>
      /// Gets the organization context for the "current user".
      /// </summary>
      /// <value>The organization context.</value>
      internal CrmContext OrganizationContext
      {
        get
        {
          return new CrmContext(this.OrganizationService);
        }
      }

      /// <summary>
      /// Gets the organization context for a system user.
      /// </summary>
      /// <value>The organization context as system user.</value>
      internal CrmContext OrganizationContextAsSystemUser
      {
        get
        {
          return new CrmContext(this.OrganizationServiceAsSystemUser);
        }
      }

      /// <summary>
      /// Gets the name of the base entity logical.
      /// </summary>
      /// <value>The name of the base entity logical.</value>
      internal string BaseEntityLogicalName
      {
        get
        {
          if (this.inputTargetEntityName == null)
          {
            var tempEntity = new TEntity();
            this.inputTargetEntityName = tempEntity.LogicalName ?? string.Empty;
          }

          return this.inputTargetEntityName;
        }
      }

      /// <summary>
      /// Gets or sets the input target.
      /// </summary>
      /// <value>The input target.</value>
      internal TEntity InputTarget
      {
        get
        {
          if (this.inputTarget == null)
          {
            if (this.InputTargetEntity != null)
            {
              this.inputTarget = this.InputTargetEntity.ToEntity<TEntity>();
            }
          }

          return this.inputTarget;
        }

        set
        {
          this.inputTargetEntity = value;
        }
      }

      /// <summary>
      /// Gets or sets the input target entity.
      /// </summary>
      /// <value>The input target entity.</value>
      internal Entity InputTargetEntity
      {
        get
        {
          if (this.inputTargetEntity == null)
          {
            this.inputTargetEntity = this.GetInputParameter<Entity>(TargetKey);
          }

          return this.inputTargetEntity;
        }

        set
        {
          this.inputTargetEntity = value;
          var inputParameters = this.PluginExecutionContext.InputParameters;
          inputParameters[TargetKey] = value;
        }
      }

      /// <summary>
      /// Gets or sets the output business entity.
      /// </summary>
      /// <value>The output business entity.</value>
      internal TEntity OutputBusinessEntity
      {
        get
        {
          if (this.outputBusinessEntity == null)
          {
            if (this.OutputBusinessEntityEntity != null)
            {
              this.outputBusinessEntity = this.OutputBusinessEntityEntity.ToEntity<TEntity>();
            }
          }

          return this.outputBusinessEntity;
        }
        
        set
        {
          this.OutputBusinessEntityEntity = value;
        }
      }

      /// <summary>
      /// Gets or sets the output business entity entity (as Entity).
      /// </summary>
      /// <value>The output business entity entity.</value>
      internal Entity OutputBusinessEntityEntity
      {
        get
        {
          if (this.outputBusinessEntityEntity == null)
          {
            this.outputBusinessEntityEntity = this.GetOutputParameter<Entity>(BusinessEntityKey);
          }

          return this.outputBusinessEntityEntity;
        }

        set
        {
          this.outputBusinessEntityEntity = value;
          var outputParameters = this.PluginExecutionContext.OutputParameters;
          outputParameters[BusinessEntityKey] = value;
        }
      }

      /// <summary>
      /// Gets the input target entity reference.
      /// </summary>
      /// <value>The input target entity reference.</value>
      internal EntityReference InputTargetEntityReference
      {
        get
        {
          if (this.inputTargetEntityReference == null)
          {
            this.inputTargetEntityReference = this.GetInputParameter<EntityReference>(TargetKey);
          }

          return this.inputTargetEntityReference;
        }
      }

      /// <summary>
      /// Gets the entity moniker 
      /// </summary>
      /// <value>The entity moniker.</value>
      internal EntityReference InputEntityMoniker
      {
        get
        {
          if (this.inputEntityMoniker == null)
          {
            this.inputEntityMoniker = this.GetInputParameter<EntityReference>(EntityMonikerKey);
          }

          return this.inputEntityMoniker;
        }
      }

      /// <summary>
      /// Gets the state of the input.
      /// </summary>
      /// <value>The state of the input.</value>
      internal OptionSetValue InputState
      {
        get
        {
          if (this.inputState == null)
          {
            this.inputStatus = this.GetInputParameter<OptionSetValue>(StateKey);
          }

          return this.inputState;
        }
      }

      /// <summary>
      /// Gets the input status.
      /// </summary>
      /// <value>The input status.</value>
      internal OptionSetValue InputStatus
      {
        get
        {
          if (this.inputStatus == null)
          {
            this.inputStatus = this.GetInputParameter<OptionSetValue>(StatusKey);
          }

          return this.inputStatus;
        }
      }

      /// <summary>
      /// Gets the relationship.
      /// </summary>
      /// <value>The relationship.</value>
      internal Relationship Relationship
      {
        get
        {
          if (this.relationship == null)
          {
            this.relationship = this.GetInputParameter<Relationship>(RelationshipKey);
          }

          return this.relationship;
        }
      }

      /// <summary>
      /// Gets the related entities.
      /// </summary>
      /// <value>The related entities.</value>
      internal EntityReferenceCollection RelatedEntities
      {
        get
        {
          if (this.relatedEntities == null)
          {
            this.relatedEntities = this.GetInputParameter<EntityReferenceCollection>(RelatedEntitiesKey);
          }

          return this.relatedEntities;
        }
      }

      /// <summary>
      /// Gets the ParameterXML parameter.
      /// </summary>
      /// <value>The parameter XML.</value>
      internal string ParameterXml
      {
        get
        {
          if (this.parameterXml == null)
          {
            this.parameterXml = this.GetInputParameter<string>(ParameterXmlKey);
          }

          return this.parameterXml;
        }
      }

      /// <summary>
      /// Resets the input target entity.
      /// </summary>
      internal void ResetInputTargetEntity()
      {
        this.inputTarget = this.InputTargetEntity.ToEntity<TEntity>();
      }

      /// <summary>
      /// Determines whether the specified message name contains message.
      /// </summary>
      /// <param name="messageName">Name of the message.</param>
      /// <returns>true in case it contains the message</returns>
      internal bool IsMessage(MessageName messageName)
      {
        return this.PluginExecutionContext.MessageName != null && this.PluginExecutionContext.MessageName == messageName.ToString();
      }

      /// <summary>
      /// Traces the specified message.
      /// </summary>
      /// <param name="message">The message.</param>
      internal void Trace(string message)
      {
        if (string.IsNullOrWhiteSpace(message) || this.TracingService == null)
        {
          return;
        }

        if (this.PluginExecutionContext == null)
        {
          this.TracingService.Trace(message);
        }
        else
        {
          this.TracingService.Trace(
            "{0}, Correlation Id: {1}, Initiating User: {2}",
            message,
            this.PluginExecutionContext.CorrelationId,
            this.PluginExecutionContext.InitiatingUserId);
        }
      }

      /// <summary>
      /// Gets the pre image entity.
      /// </summary>
      /// <param name="imageName">Name of the image.</param>
      /// <returns>
      /// Pre Image Entity
      /// </returns>
      internal Entity GetPreImageEntity(string imageName = null)
      {
        imageName = imageName ?? DefaultPreImageName;
        return this.GetImageEntity(imageName, true);
      }

      /// <summary>
      /// Gets the pre image.
      /// </summary>
      /// <param name="imageName">Name of the image.</param>
      /// <returns>
      /// Pre Image
      /// </returns>
      internal TEntity GetPreImage(string imageName = null)
      {
        var entity = this.GetPreImageEntity(imageName);
        return entity == null ? null : entity.ToEntity<TEntity>();
      }

      /// <summary>
      /// Gets the post image entity.
      /// </summary>
      /// <param name="imageName">Name of the image.</param>
      /// <returns>
      /// Post Image Entity
      /// </returns>
      internal Entity GetPostImageEntity(string imageName = null)
      {
        imageName = imageName ?? DefaultPostImageName;
        return this.GetImageEntity(imageName, false);
      }

      /// <summary>
      /// Gets the post image.
      /// </summary>
      /// <param name="imageName">Name of the image.</param>
      /// <returns>
      /// Post Image
      /// </returns>
      internal TEntity GetPostImage(string imageName = null)
      {
        var entity = this.GetPostImageEntity(imageName);
        return entity == null ? null : entity.ToEntity<TEntity>();
      }

      /// <summary>
      /// Gets the extended target entity.
      /// <remarks>
      /// It contains both old and new property values. (The old ones are from PreImage, the new ones are from Target entity
      /// </remarks>
      /// </summary>
      /// <returns>ExtendedEntity{`0}.</returns>
      internal ExtendedEntity<TEntity> BuildExtendedTargetEntity()
      {
        var result = new ExtendedEntity<TEntity>(this.InputTarget, this.GetPreImage());
        return result;
      }

      /// <summary>
      /// Gets the extended post image entity.
      /// <remarks>
      /// It contains both old and new property values. (The old ones are from PreImage, the new ones are from PostImage
      /// </remarks>
      /// </summary>
      /// <returns>ExtendedEntity{`0}.</returns>
      internal ExtendedEntity<TEntity> BuildExtendedPostImageEntity()
      {
        var result = new ExtendedEntity<TEntity>(this.GetPostImage(), this.GetPreImage());
        return result;
      }

      /// <summary>
      /// Gets the shared variable.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="useParentContext">if set to <c>true</c> [use parent context].</param>
      /// <remarks>
      /// For a plug-in registered in stage 20 or 40, to access the shared variables from a stage 10 registered plug-in 
      /// that executes on create, update, delete, or by a RetrieveExchangeRateRequest, you must access 
      /// the ParentContext.SharedVariables collection. 
      /// For all other cases, IPluginExecutionContext.SharedVariables contains the collection
      /// </remarks>
      /// <returns>Shared Variable value</returns>
      internal object GetSharedVariable(string name, bool useParentContext = false)
      {
        var context = useParentContext ? this.PluginExecutionContext.ParentContext : this.PluginExecutionContext;

        return 
          context.SharedVariables.Contains(name) ? 
            context.SharedVariables[name] : null;
      }

      /// <summary>
      /// Adds the shared variable.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="value">The value.</param>
      internal void AddSharedVariable(string name, object value)
      {
        this.PluginExecutionContext.SharedVariables.Add(name, value);
      }

      /// <summary>
      /// Gets the input parameter.
      /// </summary>
      /// <typeparam name="TK">The type of the TK.</typeparam>
      /// <param name="key">The key.</param>
      /// <returns>An instance of the TK</returns>
      private TK GetInputParameter<TK>(string key)
      {
        var parameters = this.PluginExecutionContext.InputParameters;
        return this.GetParameter<TK>(parameters, key);
      }

      /// <summary>
      /// Gets the output parameter.
      /// </summary>
      /// <typeparam name="TK">The type of the TK.</typeparam>
      /// <param name="key">The key.</param>
      /// <returns>An instance of the TK</returns>
      private TK GetOutputParameter<TK>(string key)
      {
        var parameters = this.PluginExecutionContext.OutputParameters;
        return this.GetParameter<TK>(parameters, key);
      }

      /// <summary>
      /// Gets the parameter.
      /// </summary>
      /// <typeparam name="TK">The type of the TK.</typeparam>
      /// <param name="parameters">The parameters.</param>
      /// <param name="key">The key.</param>
      /// <returns>An instance of the TK</returns>
      private TK GetParameter<TK>(ParameterCollection parameters, string key)
      {
        if (!parameters.ContainsKey(key))
        {
          return default(TK);
        }

        return (TK)parameters[key];
      }

      /// <summary>
      /// Gets the entity image.
      /// </summary>
      /// <param name="imageName">Name of the image.</param>
      /// <param name="preEntity">if set to <c>true</c> then it's PreEntity, otherwise it's PostImage.</param>
      /// <returns>The entity image.</returns>
      private Entity GetImageEntity(string imageName, bool preEntity)
      {
        var images =
          preEntity ?
          this.PluginExecutionContext.PreEntityImages :
          this.PluginExecutionContext.PostEntityImages;

        if (images.Contains(imageName) && images[imageName] != null)
        {
          return images[imageName];
        }

        return null;
      }

      /// <summary>
      /// Gets the image casted to TEntity.
      /// </summary>
      /// <param name="imageName">Name of the image.</param>
      /// <param name="preEntity">if set to <c>true</c> [pre entity].</param>
      /// <returns>The entity of TEntity </returns>
      private TEntity GetImage(string imageName, bool preEntity)
      {
        var entity = this.GetImageEntity(imageName, preEntity);
        return entity == null ? null : entity.ToEntity<TEntity>();
      }
    }

    /// <summary>
    /// Defines the base Plugin Step class
    /// <remarks>
    /// Use it to register step event in the derived plugin class
    /// </remarks>
    /// </summary>
    public class PluginStepBase
    {
      /// <summary>
      /// Gets or sets the pipeline stage.
      /// </summary>
      /// <value>
      /// The pipeline stage.
      /// </value>
      public Stage? Stage { get; set; }

      /// <summary>
      /// Gets or sets the execution mode.
      /// </summary>
      /// <value>
      /// The mode.
      /// </value>
      public Mode? Mode { get; set; }

      /// <summary>
      /// Gets or sets the name of the message.
      /// </summary>
      /// <value>
      /// The name of the message.
      /// </value>
      public MessageName? MessageName { get; set; }

      /// <summary>
      /// Gets or sets the action that fires for the event.
      /// </summary>
      /// <value>
      /// The handler.
      /// </value>
      public Action<LocalPluginContext> Handler { get; set; }
    }
  }
}